package me.magicall.db.springjdbc.dao2;

import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import me.magicall.coll.CollFactory;
import me.magicall.db.Condition;
import me.magicall.db.ConditionOperator;
import me.magicall.db.SqlValueAppender;
import me.magicall.db.dao.CommonDao;
import me.magicall.db.dao2.AbsDaoImpl;
import me.magicall.db.dao2.Query;
import me.magicall.db.meta.DbColumn;
import me.magicall.db.meta.ForeignKey;
import me.magicall.db.meta.TableMeta;
import me.magicall.db.meta.TableMetaAccessor;
import me.magicall.db.sql.As;
import me.magicall.db.sql.from.From;
import me.magicall.db.sql.from.TableWithAs;
import me.magicall.db.sql.table.Table;
import me.magicall.db.util.DbUtil;
import me.magicall.db.util.DbUtil.DbTypeConvert;
import me.magicall.util.BeanUtil;
import me.magicall.util.MethodUtil;
import me.magicall.util.kit.Kits;

import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;

/**
 * 目前还不支持跨表联查.
 * 如果A类对象有个B类字段,并且A和B都是表,并且A和B表有外键关联,则将对应的外键关联加入到查询条件中,通常是A.bId=B.id,无需A类对象有bId字段
 * 
 * @author MaGiCalL
 * @param <T>
 */
//modelClass的短名字即是表名
//拼接select部分时用 model名$字段名 的形式
//根据Query对象的
public class DaoImpl<T> extends AbsDaoImpl<T> {

	/**
	 * 用来拼接modelName和fieldName的连接符.数据库标签不支持使用点号(.),但支持$.
	 */
	private static final char SEPERATOR = '$';

	private final TableMetaAccessor tableMetaAccessor;
	private final NamedParameterJdbcOperations namedJdbc;

	protected final Class<T> mainModelClass;

	public DaoImpl(final TableMetaAccessor tableMetaAccessor, final NamedParameterJdbcOperations jdbc,//
			final CommonDao<T> commonDao, final Class<T> mainModelClass) {
		super(commonDao == null ? new CommonDaoImpl<T>(tableMetaAccessor, jdbc) : commonDao);
		this.mainModelClass = mainModelClass;
		this.tableMetaAccessor = tableMetaAccessor;
		this.namedJdbc = jdbc;
	}

	public DaoImpl(final TableMetaAccessor tableMetaAccessor, final NamedParameterJdbcOperations jdbc, final Class<T> mainModelClass) {
		this(tableMetaAccessor, jdbc, new CommonDaoImpl<T>(tableMetaAccessor, jdbc), mainModelClass);
	}

	//========================================================
	@Override
	public int count(final Query<T> query) {
		final Class<T> mainModelClass = mainModelClass(query);
		final TableMeta tableMeta = getTableMeta(mainModelClass);
		if (tableMeta == null) {//并无此表
			return 0;
		}

		//结果集中的字段名与model的setter方法的映射关系
		final Map<String, Method> resultSetterMapping = new HashMap<String, Method>();
		//结果集中的表名与model的Class对象的映射关系
		final Map<String, Class<?>> modelClassMap = new HashMap<String, Class<?>>();
		//需要拼接在select的字段名
		final List<String> selectFields = new LinkedList<String>();
		//需要拼接在from的表名
//		final List<String> fromTables = new LinkedList<String>();
		final From from = new From();
		//需要拼接在where的外键名
		final List<String> whereForeignKey = new LinkedList<String>();
		//TODO:写一个prepareMappingForCount
		prepareMappingForList(mainModelClass, mainModelName(), tableMeta, resultSetterMapping, modelClassMap, selectFields, from, whereForeignKey);

		//select
		final StringBuilder sb = new StringBuilder("select count(1)");
//		Kits.STR.join(sb, selectFields, ",");
		//from
//		sb.append(" from ");
//		Kits.STR.join(sb, fromTables, ",");
		from.appendTo(sb);
		//where
		final Map<String, Object> namedParamValueMap = appendWhere(sb, tableMeta, query.getConditions());
		//拼接表间关联字段
		Kits.STR.join(sb, whereForeignKey, " AND ");
		//order by
		DbUtil.appendOrderBy(sb, query.getFieldComparator());
		//limit
		DbUtil.appendLimit(sb, query.getPageInfo());
		//select T1.name AS T1$name,T1.id AS T1$id from t1 AS T1 where id=':id#0' limit 1
		return getNamedJdbc().queryForInt(sb.toString(), namedParamValueMap);
	}

	@Override
	public List<T> list(final Query<T> query) {
		final Class<T> mainModelClass = mainModelClass(query);
		final TableMeta tableMeta = getTableMeta(mainModelClass);
		if (tableMeta == null) {//并无此表
			return Kits.LIST.emptyValue();
		}

		//结果集中的字段名与model的setter方法的映射关系
		final Map<String, Method> resultSetterMapping = new HashMap<String, Method>();
		//结果集中的表名与model的Class对象的映射关系
		final Map<String, Class<?>> modelClassMap = new HashMap<String, Class<?>>();
		//需要拼接在select的字段名
		final List<String> selectFields = new LinkedList<String>();
		//需要拼接在from的表名
		final From from = new From();
		//需要拼接在where的外键名
		final List<String> whereForeignKey = new LinkedList<String>();

		prepareMappingForList(mainModelClass, mainModelName(), tableMeta, resultSetterMapping, modelClassMap,//
				selectFields, from, whereForeignKey);

		//select
		final StringBuilder sb = new StringBuilder("select ");
		Kits.STR.join(sb, selectFields, ",");
		//from
//		sb.append(" from ");
//		Kits.STR.join(sb, fromTables, ",");
		from.appendTo(sb);
		//where
		final Map<String, Object> namedParamValueMap = appendWhere(sb, tableMeta, query.getConditions());
		//拼接表间关联字段
		Kits.STR.join(sb, whereForeignKey, " AND ");
		//order by
		DbUtil.appendOrderBy(sb, query.getFieldComparator());
		//limit
		DbUtil.appendLimit(sb, query.getPageInfo());
		//select T1.name AS T1$name,T1.id AS T1$id from t1 AS T1 where id=':id#0' limit 1
		final List<T> list = getNamedJdbc().query(sb.toString(), namedParamValueMap, new RowMapper<T>() {
			@Override
			public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
				return mapRow0(modelClassMap, resultSetterMapping, rs, rowNum);
			}
		});
		return list;
	}

	private T mapRow0(final Map<String, Class<?>> modelClassMap, final Map<String, Method> resultMapping, final ResultSet rs, final int rowNum)
			throws SQLException {
		final Map<String, Object> modelMap = new HashMap<String, Object>();
		final T mainModel = newModel();
		modelMap.put(mainModelName(), mainModel);

		final ResultSetMetaData resultSetMetaData = rs.getMetaData();
		final int columnCount = resultSetMetaData.getColumnCount();
		//将返回结果填到setter中.
		for (int i = DbUtil.RESULT_SET_COLUMN_START_INDEX; i <= columnCount; ++i) {
			final String label = resultSetMetaData.getColumnLabel(i);
			final Method setter = resultMapping.get(label);
			if (setter == null) {
				//理论上不会到达.
				System.out.println("@@@@@@DaoImpl.mapRow():setter null");
				continue;
			}
			//创建model
			final int dotIndex = label.lastIndexOf(SEPERATOR);
			if (dotIndex > 0) {//xxx.yyy,此字段不是主model的字段,而是另一个关联model的字段.
				//注:来到这里,点后面的字段名是as过的,是java name,不需要再转换成java name了
				//还有一个问题,如果主model为a,a中有一个b,b中有一个c,b和c是另外的对象,此字段是c的字段,则a中无c,结果不对.
				final String modelName = label.substring(0, dotIndex);

				Object model = modelMap.get(modelName);
				if (model == null) {
					final Class<?> modelClass = modelClassMap.get(modelName);
					model = BeanUtil.newBean(modelClass);
					modelMap.put(modelName, model);
				}

				//调用setter
				final Class<?> setterParamClass = setter.getParameterTypes()[0];
				final DbTypeConvert dbTypeConvert = DbUtil.getDbTypeConvert(setterParamClass);
				MethodUtil.invokeMethod(model, setter, dbTypeConvert.convert(rs, i));
			}//else //理论上不会到达
		}//for
			//构建model树.把散列在map中的model们放到它的容器model中.
		for (final Entry<String, Object> e : modelMap.entrySet()) {
			final String modelName = e.getKey();
			final int dotIndex = modelName.lastIndexOf(SEPERATOR);
			if (dotIndex > 0) {
				final String containerModelName = modelName.substring(0, dotIndex);
				final Method setter = resultMapping.get(containerModelName);
				if (setter != null) {
					final Object model = e.getValue();
					final Object containerModel = modelMap.get(containerModelName);
					MethodUtil.invokeMethod(containerModel, setter, model);
				}
			}
		}//for
		return mainModel;
	}

	/**
	 * 处理映射关系.select的各个字段与setter的映射关系;外键关联关系
	 * 
	 * @param modelClass
	 * @param modelName
	 * @param tableMeta
	 * @param resultSetterMaping
	 * @param modelClassMap
	 * @param selectFields
	 * @param fromTables
	 * @param whereForeignKey
	 */
	private void prepareMappingForList(final Class<?> modelClass, final String modelName, final TableMeta tableMeta,//
			final Map<String, Method> resultSetterMaping, final Map<String, Class<?>> modelClassMap,//
			final Collection<String> selectFields, final From from,//final Collection<String> fromTables,//
			final List<String> whereForeignKey) {
		final String tableName = tableMeta.getName();
		modelClassMap.put(modelName, modelClass);
//		fromTables.add(tableName + " AS " + modelName);//加入到from中
		from.add(new TableWithAs(new Table(tableName), new As(modelName)));

		final Collection<Method> setters = MethodUtil.getAllSettersForField(modelClass);
		for (final Method setter : setters) {//遍历每一个带setter的字段
			final Class<?> fieldClass = setter.getParameterTypes()[0];
			final String fieldName = BeanUtil.setterNameToFieldName(setter.getName());

			if (DbUtil.isDbType(fieldClass)) {//此字段是数据库基础类型
				final String columnName = DbUtil.javaNameToDbName(fieldName);
				final Collection<String> columnNames = tableMeta.getColumnNames();
				if (columnNames.contains(columnName)) {//此字段也是数据库的一列.
					final String resultLabel = modelName + SEPERATOR + fieldName;
					resultSetterMaping.put(resultLabel, setter);

					//加入到select中.table_name.column_name AS modelName$fieldName.
					//注意"."前面使用的是modelName,因为在from时把表名as过了
					selectFields.add(modelName + "." + columnName + " AS " + resultLabel);
				}//else 若不是数据库的一列,也不处理
			} else {//是一个model
				handleModel(modelName, tableMeta, resultSetterMaping, modelClassMap, selectFields, from, whereForeignKey, setter, fieldClass, fieldName);
			}
		}
	}

	/**
	 * @param modelName
	 * @param tableMeta
	 * @param resultSetterMaping
	 * @param modelClassMap
	 * @param selectFields
	 * @param fromTables
	 * @param whereForeignKey
	 * @param setter
	 * @param fieldClass
	 * @param fieldName
	 */
	private void handleModel(final String modelName, final TableMeta tableMeta, final Map<String, Method> resultSetterMaping,
			final Map<String, Class<?>> modelClassMap, final Collection<String> selectFields, final From from,// final Collection<String> fromTables,
			final List<String> whereForeignKey, final Method setter, final Class<?> fieldClass, final String fieldName) {
		final TableMeta fieldTableMeta = getTableMeta(fieldClass);
		if (fieldTableMeta != null) {
			final String refedModelName = modelName + SEPERATOR + fieldName;
			resultSetterMaping.put(refedModelName, setter);
			//寻找外键
			final Collection<ForeignKey> foreignKeys = tableMeta.getForeignKeys();
			for (final ForeignKey foreignKey : foreignKeys) {
				if (foreignKey.getReferencedTable().equals(fieldTableMeta)) {
					final DbColumn referencingColumn = foreignKey.getReferencingColumn();
					final StringBuilder sb = new StringBuilder();
					final String referencingColumnName = referencingColumn.getName();
					sb.append(modelName).append(".").append(referencingColumnName).append("=")//
							.append(foreignKey.getReferencedTable().getName()).append(".").append(foreignKey.getReferencedColumn().getName());
					whereForeignKey.add(sb.toString());
					break;
				}
			}
			prepareMappingForList(fieldClass, refedModelName, fieldTableMeta, resultSetterMaping, modelClassMap,//
					selectFields, from, whereForeignKey);
		}
	}

	@Override
	public int del(final Query<T> query) {
		final TableMeta tableMeta = getTableMeta(mainModelClass(query));
		final StringBuilder sb = DbUtil.buildDeleteFromTable(mainModelName());
		//where
		final List<Condition> conditions = query.getConditions();
		final Map<String, Object> namedParamValueMap = appendWhere(sb, tableMeta, conditions);
		return getNamedJdbc().update(sb.toString(), namedParamValueMap);
	}

	protected static Map<String, Object> appendWhere(final StringBuilder sb, final TableMeta tableMeta, final List<Condition> conditions) {
		if (Kits.COLL.isEmpty(conditions)) {
			return Kits.MAP.emptyValue();
		}
		sb.append(" where ");
		final Map<String, Object> namedParamValueMap = new HashMap<String, Object>();
		boolean first = true;
		for (final Condition condition : conditions) {
			if (first) {
				first = false;
			} else {
				sb.append(" and ");
			}
			final ConditionOperator operator = condition.getConditionOperator();
			final String fieldName = condition.getFieldName();
			final List<?> refedValues = condition.getRefedValues();
			operator.buildSql(sb, fieldName, new SqlValueAppender() {
				@Override
				public void appendValue(final StringBuilder sb, final ConditionOperator conditionOperator,//
						final String fieldName, final int index, final Object refedValue) {
					final String paramedName = fieldName + "#" + index;
					namedParamValueMap.put(paramedName, conditionOperator.facadeValue(refedValue));
					sb.append(DbUtil.namedParam(paramedName));
				}
			}, refedValues);
		}
		return namedParamValueMap;
	}

	private int delById(final Collection<? extends Object> idColl, final Object[] idArr) {
		final StringBuilder sb = DbUtil.buildDeleteFromTable(mainModelName());
		sb.append(" where ").append(idColumnName());
		DbUtil.appendInPlaceHolder(sb, idColl);
		return getJdbcOperations().update(sb.toString(), idArr);
	}

	@Override
	public int delById(final Object... id) {
		return delById(CollFactory.L.asList(id), id);
	}

	@Override
	public int delById(final Collection<? extends Object> id) {
		return delById(id, id.toArray(new Object[id.size()]));
	}

	protected TableMeta getTableMeta(final Class<?> modelClass) {
		return getTableMetaAccessor().getTableMetaUsingModelName(modelClass.getSimpleName());
	}

	protected T newModel() {
		return BeanUtil.newBean(mainModelClass);
	}

	protected String mainModelName() {
		return modelClassName(mainModelClass);
	}

	protected Class<T> mainModelClass(final Query<T> query) {
		return mainModelClass;
	}

	protected TableMetaAccessor getTableMetaAccessor() {
		return tableMetaAccessor;
	}

	protected NamedParameterJdbcOperations getNamedJdbc() {
		return namedJdbc;
	}

	protected JdbcOperations getJdbcOperations() {
		return getNamedJdbc().getJdbcOperations();
	}

//private String appendFields(final StringBuilder sb, final Class<?> c, final boolean usingAs) {
//	final String refedTableName = DbUtil.javaNameToDbName(modelClassName(c));
//	//字段
//	final Collection<Method> getters = MethodUtil.getAllGettersWithoutGetClass(c);
//	for (final Method getter : getters) {
//		final String getterName = getter.getName();
//		final String fieldName = BeanUtil.getterNameToFieldName(getterName);
//		sb.append(refedTableName).append(".").append(DbUtil.javaNameToDbName(fieldName));
//		if (usingAs) {
//			sb.append(" as ").append(refedTableName).append(".").append(fieldName);
//		}
//		sb.append(",");
//	}
//	return refedTableName;
//}
//	private static final String[] MODEL_FIELD_SEPERATOR = { ".", "'s ", "' " };
//	private String[] seperator(final String fieldName) {
//		for (final String seperator : MODEL_FIELD_SEPERATOR) {
//			final int index = fieldName.indexOf(seperator);
//			if (index == 0) {
//				throw new IllegalArgumentException(fieldName + " starts with a " + seperator);
//			}
//			if (index > 0) {
//				return new String[] { seperator.substring(0, index), seperator.substring(index + seperator.length()) };
//			}
//		}
//		return null;
//	}
}
